﻿using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using System.Text;

namespace AutoRevit.Generator;

/// <summary>
/// 此类是一个源代码生成器，用于生成外部事件处理程序的代码。
/// </summary>
[Generator]
public class ExternalEventHandlerGenerator : ISourceGenerator
{
    private const string attributeText = $@"#pragma warning disable
#nullable enable

namespace AutoRevit;

[AttributeUsage(AttributeTargets.Method)]
[System.Diagnostics.Conditional(""AutoExternalEventHandlerGenerator_DEBUG"")]
internal sealed class AutoExternalEventHandlerAttribute : Attribute
{{
    public string? Namespace {{ get; set; }}
    public string? ClassName {{ get; set; }}
    public string? EventName {{ get; set; }}
}}";

    /// <summary>
    /// 在每次世代传递前按需创建
    /// </summary>
    private class SyntaxContextReceiver : ISyntaxContextReceiver
    {
        public List<IMethodSymbol> MethodSymbols { get; } = [];

        /// <summary>
        /// 在编译过程中针对每一个语法节点发出请求，我们能够检查这些节点并保存任何有助于生成过程的信息。
        /// </summary>
        public void OnVisitSyntaxNode(GeneratorSyntaxContext context)
        {
            if (context.Node is not MethodDeclarationSyntax methodDeclarationSyntax || methodDeclarationSyntax.AttributeLists.Count == 0)
            {
                return;
            }

            if (context.SemanticModel.GetDeclaredSymbol(methodDeclarationSyntax) is not IMethodSymbol methodSymbol)
            {
                return;
            }

            // 检查方法是否具有特定属性
            if (methodSymbol.GetAttributes().Any(ad => ad.AttributeClass!.ToDisplayString() == "AutoRevit.AutoExternalEventHandlerAttribute"))
            {
                MethodSymbols.Add(methodSymbol);
            }
        }
    }

    private static readonly DiagnosticDescriptor parametersDescriptor = new (id: "ARG005",
                                                                             title: "Invalid Method Symbol",
                                                                             messageFormat: "The method '{0}' does not meet the required conditions for code generation. The input parameter quantity is one, and its type must be 'Autodesk.Revit.UI.UIApplication'.",
                                                                             category: "AutoRevit.Generator",
                                                                             DiagnosticSeverity.Error,
                                                                             isEnabledByDefault: true);

    public void Initialize(GeneratorInitializationContext context)
    {
        context.RegisterForPostInitialization((i) =>
        {
            i.AddSource("AutoExternalEventHandlerAttribute.g.cs", SourceText.From(attributeText, Encoding.UTF8));
        });

        context.RegisterForSyntaxNotifications(() => new SyntaxContextReceiver());
    }

    public void Execute(GeneratorExecutionContext context)
    {
        if (context.SyntaxContextReceiver is not SyntaxContextReceiver syntaxContextReceiver)
        {
            return;
        }

        INamedTypeSymbol attributeSymbol = context.Compilation.GetTypeByMetadataName("AutoRevit.AutoExternalEventHandlerAttribute")!;
        INamedTypeSymbol uiApplicationType = context.Compilation.GetTypeByMetadataName("Autodesk.Revit.UI.UIApplication")!;

        

        foreach (IMethodSymbol methodSymbol in syntaxContextReceiver.MethodSymbols)
        {
            // 检查方法符号是否符合条件
            int failedValidationCount = 0;
            // 检查方法符号是否符合条件
            if (!ReportDiagnostic.ValidIsStatic(context, methodSymbol))
            {
                failedValidationCount++;
            }

            if (!ReportDiagnostic.ValidReturnsVoid(context, methodSymbol))
            {
                failedValidationCount++;
            }

            if (!ReportDiagnostic.ValidIsPublicOrInternal(context, methodSymbol))
            {
                failedValidationCount++;
            }

            if (methodSymbol.Parameters.Length != 1)
            {
                context.ReportDiagnostic(Diagnostic.Create(
                    parametersDescriptor,
                    methodSymbol.Locations.FirstOrDefault(),
                    methodSymbol.Name));

                failedValidationCount++;
            }
            else
            {
                IParameterSymbol parameterSymbol = methodSymbol.Parameters[0];

                if (!SymbolEqualityComparer.Default.Equals(uiApplicationType, parameterSymbol.Type))
                {
                    context.ReportDiagnostic(Diagnostic.Create(
                        parametersDescriptor,
                        methodSymbol.Locations.FirstOrDefault(),
                        methodSymbol.Name));

                    failedValidationCount++;
                }
            }

            if (failedValidationCount > 0)
            {
                continue;
            }

            var classSource = ProcessClass(methodSymbol, attributeSymbol);

            context.AddSource($"{methodSymbol.Name}_AutoExternalEventHandler.g.cs", SourceText.From(classSource!, Encoding.UTF8));
        }
    }

    private string ProcessClass(IMethodSymbol methodSymbol, INamedTypeSymbol attributeSymbol)
    {
        var namespaceName = SymbolUtils.GetAttributePropertyValue(methodSymbol, attributeSymbol, "Namespace") ?? methodSymbol.ContainingNamespace.ToDisplayString();
        var className = SymbolUtils.GetAttributePropertyValue(methodSymbol, attributeSymbol, "ClassName") ?? $"{methodSymbol.Name}AutoExternalEventHandler";
        var eventName = SymbolUtils.GetAttributePropertyValue(methodSymbol, attributeSymbol, "EventName") ?? methodSymbol.Name;

        string classSource = $@"using global::Autodesk.Revit.UI;

namespace {namespaceName};

public class {className} : IExternalEventHandler
{{
    public void Execute(UIApplication app)
    {{
        global::{methodSymbol.ContainingType}.{methodSymbol.Name}(app);
    }}

    public string GetName()
    {{
        return ""{eventName}"";
    }}
}}";
        return classSource;
    }
}
